package han.cirno.corrupt.data

import han.cirno.corrupt.Application
import han.cirno.corrupt.data.instances.UserSchoolData
import han.cirno.corrupt.util.SerialUtil
import han.cirno.corrupt.util.StreamUtil
import java.io.File
import kotlin.reflect.KClass
import kotlin.reflect.KMutableProperty
import kotlin.reflect.full.companionObject
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.memberProperties
import kotlin.reflect.jvm.javaType

object UserUnstructuredData {
    private lateinit var filesDir:File

    private val dataClasses= arrayOf<KClass<*>>(
        UserSchoolData::class
    )

    fun init(){
        filesDir=Application.application.filesDir
    }

    fun loadData() {
        dataClasses.forEach {
            val (fileName, member)=getClassAnnotationAndInstanceMember(it)?:return@forEach
            val data = readFromFile(fileName)
            if (data!=null)
                member.setter.call(it.companionObjectInstance,data)
        }
    }

    fun saveData() {
        dataClasses.forEach {
            val (fileName, member) = getClassAnnotationAndInstanceMember(it)?:return@forEach
            val data = member.getter.call(it.companionObjectInstance)
            writeToFile(fileName, data!!)
        }
    }

    fun clearData(){
        filesDir.deleteRecursively()
        dataClasses.forEach {
            val (_, member) = getClassAnnotationAndInstanceMember(it)?:return@forEach
            member.setter.call(it.companionObjectInstance,(member.returnType.javaType as Class<*>).constructors[0].newInstance())
        }
        val instance = DB.getInstance()
        instance.run {
            examDao().deleteAll()
            dateInfoDao().deleteAll()
            classEntryDao().deleteAll()
            classTableDao().deleteAll()
        }
    }

    private fun getClassAnnotationAndInstanceMember(clazz:KClass<*>):Pair<String,KMutableProperty<*>>? {
        val dataClassAnnotation = clazz.annotations.find { it.annotationClass == UserDataFileAnn::class }?:return null
        val member = (clazz.companionObject!!.memberProperties.find {
            m -> m.annotations.find {
                a->a.annotationClass == UserDataInstance::class
            } != null }!!
                as KMutableProperty<*>)
        return Pair((dataClassAnnotation as UserDataFileAnn).name,member)
    }

    private fun readFromFile(fileName: String):Any?{
        if (!filesDir.exists() && !filesDir.mkdir()) throw RuntimeException("Error in reading or making files dir")
        var unattempted = true
        while (true) {
            val dataFile = File(filesDir, fileName + (if (unattempted) "" else ".bak"))
            try {
                return SerialUtil.deserialize(dataFile,Any::class)
            } catch (e: Exception) {
                if (unattempted) {
                    unattempted = false
                    continue
                }
                return null
            }
        }
    }

    private fun writeToFile(fileName: String, data: Any) {
        if (!filesDir.exists() && !filesDir.mkdir()) throw RuntimeException("Error in reading or making files dir")
        val dataFile = File(filesDir, fileName)
        val preDataFile = File(filesDir, "$fileName.bak")
        try {
            if (dataFile.exists()&&preDataFile.exists())
                StreamUtil.copyFile(dataFile,preDataFile)
            dataFile.createNewFile()
            SerialUtil.serialize(dataFile,data)
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}